package com.leyou.gateway.filters;

import com.leyou.common.constants.RedisConstants;
import com.leyou.common.constants.UserTokenConstants;
import com.leyou.common.entity.Payload;
import com.leyou.common.entity.UserInfo;
import com.leyou.common.exceptions.LyException;
import com.leyou.common.utils.JwtUtils;
import com.leyou.gateway.config.FilterProperties;
import com.leyou.gateway.config.JwtProperties;
import io.jsonwebtoken.JwtException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author 虎哥
 */
@Component
public class LoginFilter implements GlobalFilter, Ordered {

    private final JwtProperties jwtProp;
    private final FilterProperties filterProp;

    private final StringRedisTemplate redisTemplate;

    public LoginFilter(JwtProperties jwtProp, FilterProperties filterProp, StringRedisTemplate redisTemplate) {
        this.jwtProp = jwtProp;
        this.filterProp = filterProp;
        this.redisTemplate = redisTemplate;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        try {
            // 1.获取Request对象
            ServerHttpRequest request = exchange.getRequest();
            // 2.获取cookie
            HttpCookie cookie = request.getCookies().getFirst(UserTokenConstants.COOKIE_NAME);
            if (cookie == null) {
                throw new LyException(401, "未登录或状态无效！");
            }
            // 3.校验token是否有效
            String token = cookie.getValue();
            try {
                // 3.1.解析并验证token
                Payload<UserInfo> payload = JwtUtils.getInfoFromToken(token, jwtProp.getPublicKey(), UserInfo.class);
                // 3.2.获取用户
                UserInfo userInfo = payload.getUserInfo();

                // 4.验证JTI
                // 4.1.取出redis中JTI
                String cacheJTI = redisTemplate.opsForValue().get(RedisConstants.JTI_KEY_PREFIX + userInfo.getId());
                // 4.2.获取token中的JTI
                String jti = payload.getId();
                // 4.3.比较
                if (!StringUtils.equals(jti, cacheJTI)) {
                    // JTI不同，无效token
                    throw new LyException(400, "用户未登录或登录超时！");
                }

                // 刷新token有效期
                redisTemplate.expire(RedisConstants.JTI_KEY_PREFIX + userInfo.getId(),
                        RedisConstants.TOKEN_EXPIRE_MINUTES, TimeUnit.MINUTES);
                // TODO 权限判断
            } catch (JwtException e) {
                // 解析失败，token有误
                throw new LyException(401, "未登录或状态无效！");
            }
            // 5.有效，放行
            return chain.filter(exchange);
        } catch (LyException e) {
            // 6.登录无效，判断是否在白名单中，如果在也要放行
            if (isAllowRequest(exchange)) {
                // 在白名单许可中，放行
                return chain.filter(exchange);
            }

            // 状态码为401，未授权
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            // 拦截请求
            return exchange.getResponse().setComplete();
        }
    }

    private boolean isAllowRequest(ServerWebExchange exchange) {
        // 1.获取当前请求Request
        ServerHttpRequest request = exchange.getRequest();
        // 2.获取请求方式和请求路径
        String method = request.getMethodValue();// 方式：GET
        String path = request.getPath().toString(); // 路径： /item/brand/2032
        // 3.遍历白名单
        for (Map.Entry<String, Set<String>> entry : filterProp.getAllowRequests().entrySet()) {
            // 白名单路径前缀
            String allowPathPrefix = entry.getKey();
            // 白名单methods集合
            Set<String> allowMethods = entry.getValue();
            // 判断是否符合
            if (StringUtils.startsWith(path, allowPathPrefix) && allowMethods.contains(method)) {
                // 符合白名单
                return true;
            }
        }
        // 不符合白名单
        return false;
    }

    @Override
    public int getOrder() {
        // 登录拦截，可以采用最高优先级！
        return HIGHEST_PRECEDENCE;
    }
}
